package main

import (
	"bufio"
	"crypto/tls"
	"errors"
	"fmt"
	"io"
	"net"
	"net/url"
	"strings"
	"sync"
	"time"

	"golang.org/x/net/proxy"
)

type Pop3Addr struct {
	addr   string
	port   int
	useTLS bool
}

var Pop3Server = map[string]Pop3Addr{
	"@163.com":     {"pop.163.com", 995, true},
	"@126.com":     {"pop.126.com", 995, true},
	"@yeah.net":    {"pop.yeah.net", 995, true},
	"@qq.com":      {"pop.qq.com", 995, true},
	"@sina.com":    {"pop.sina.com", 995, true},
	"@sohu.com":    {"pop.sohu.com", 995, true},
	"@21cn.com":    {"pop.21cn.com", 995, true},
	"@189.cn":      {"pop.189.cn", 995, true},
	"@188.com":     {"pop.188.com", 995, true},
	"@aliyun.com":  {"pop.mxhichina.com", 995, true}, // 阿里云邮箱（因用户配置不同，可能需确认）
	"@gmail.com":   {"pop.gmail.com", 995, true},
	"@yahoo.com":   {"pop.mail.yahoo.com", 995, true},
	"@outlook.com": {"pop-mail.outlook.com", 995, true},
	"@hotmail.com": {"pop-mail.outlook.com", 995, true},
	"@live.com":    {"pop-mail.outlook.com", 995, true},
	"@msn.com":     {"pop-mail.outlook.com", 995, true},
	"@icloud.com":  {"pop.mail.me.com", 995, true},
	"@me.com":      {"pop.mail.me.com", 995, true},
	"@aol.com":     {"pop.aol.com", 995, true},
	"@gmx.com":     {"pop.gmx.com", 995, true},
	"@mail.com":    {"pop.mail.com", 995, true},
	"@foxmail.com": {"pop.qq.com", 995, true}, // Foxmail 常用 QQ 邮箱 POP 地址（视账号归属）
}

func GetEmailPop3Server(email string) *Pop3Addr {
	for s, addr := range Pop3Server {
		if strings.HasSuffix(email, s) {
			return &addr
		}
	}
	return nil
}

type Pop3 struct {
	user     string
	password string
	conn     net.Conn
}

func NewPop3(conn net.Conn, user, password string) *Pop3 {
	return &Pop3{
		user:     user,
		password: password,
		conn:     conn,
	}
}

func (p *Pop3) SetConn(conn net.Conn) *Pop3 {
	p.conn = conn
	return p
}

func (p *Pop3) Login() error {
	if p.conn == nil {
		return fmt.Errorf("没有可用的连接")
	}
	scanner := bufio.NewScanner(p.conn)

	// 读取服务器的初始欢迎行（通常以 +OK 开头）
	if scanner.Scan() {
		line := scanner.Text()
		if strings.HasPrefix(line, "-ERR") {
			return fmt.Errorf("POP3 欢迎响应错误: %s", line)
		}
		// 若是 +OK，则继续
	} else if err := scanner.Err(); err != nil {
		return fmt.Errorf("读取欢迎响应失败: %v", err)
	}

	// 发送 USER 命令并检查响应
	if _, err := fmt.Fprintln(p.conn, fmt.Sprintf("USER %s", p.user)); err != nil {
		return fmt.Errorf("发送 USER 失败: %v", err)
	}
	if scanner.Scan() {
		line := scanner.Text()
		if strings.HasPrefix(line, "-ERR") {
			return fmt.Errorf("USER 被拒绝: %s", line)
		}
	} else if err := scanner.Err(); err != nil {
		return fmt.Errorf("读取 USER 响应失败: %v", err)
	}

	// 发送 PASS 命令并检查响应
	if _, err := fmt.Fprintln(p.conn, fmt.Sprintf("PASS %s", p.password)); err != nil {
		return fmt.Errorf("发送 PASS 失败: %v", err)
	}
	if scanner.Scan() {
		line := scanner.Text()
		if strings.HasPrefix(line, "-ERR") {
			return fmt.Errorf("PASS 被拒绝: %s", line)
		}
		if !strings.HasPrefix(line, "+OK") {
			return fmt.Errorf("未知的 PASS 响应: %s", line)
		}
	} else if err := scanner.Err(); err != nil {
		return fmt.Errorf("读取 PASS 响应失败: %v", err)
	}

	return nil
}

func (p *Pop3) IsLogin() bool {
	// 没有连接则视为未登录
	if p.conn == nil {
		return false
	}

	// 使用短超时发送 NOOP 并读取单行响应以确认连接/认证是否仍然有效
	deadline := time.Now().Add(2 * time.Second)
	_ = p.conn.SetDeadline(deadline)
	defer func() {
		// 清除 deadline，忽略错误
		_ = p.conn.SetDeadline(time.Time{})
	}()

	// 发送 NOOP 命令（不应改变服务器状态，只用于探测）
	_, err := p.conn.Write([]byte("NOOP\r\n"))
	if err != nil {
		return false
	}

	// 读取一行响应
	reader := bufio.NewReader(p.conn)
	line, err := reader.ReadString('\n')
	if err != nil {
		return false
	}

	line = strings.TrimSpace(line)
	// 有效的响应通常以 +OK 开头
	if strings.HasPrefix(line, "+OK") {
		return true
	}
	return false
}

func (p *Pop3) ListIds() ([]int, error) {
	scanner := bufio.NewScanner(p.conn)
	var ids []int

	// 只发送 UIDL，假设调用者已调用 Login() 并且处于已认证状态
	if _, err := fmt.Fprintln(p.conn, "UIDL"); err != nil {
		return nil, err
	}

	// 解析响应：UIDL 的多行响应格式通常为 "1 unique-id"
	for scanner.Scan() {
		line := scanner.Text()
		if strings.HasPrefix(line, "+OK") {
			continue
		} else if strings.HasPrefix(line, "-ERR") {
			return nil, fmt.Errorf("POP3错误: %s", line)
		} else if line == "." {
			break
		} else {
			// 只解析像 "1 <uid>" 这样的行，取编号（第一个字段）以便后续用 RETR 获取内容
			parts := strings.Fields(line)
			if len(parts) >= 2 {
				// 解析编号为整数
				var id int
				_, err := fmt.Sscanf(parts[0], "%d", &id)
				if err == nil {
					ids = append(ids, id)
				}
			}
		}
	}
	if err := scanner.Err(); err != nil {
		return nil, fmt.Errorf("读取 POP3 响应失败: %v", err)
	}
	return ids, nil
}

func (p *Pop3) ReadContent(id int) (string, error) {
	scanner := bufio.NewScanner(p.conn)
	var builder strings.Builder

	// 发送 RETR 命令
	_, err := fmt.Fprintf(p.conn, "RETR %d\r\n", id)
	if err != nil {
		return "", err
	}

	// 读取多行响应直到单独的一行 "."
	started := false
	for scanner.Scan() {
		line := scanner.Text()
		if strings.HasPrefix(line, "+OK") {
			started = true
			continue
		}
		if !started {
			// 忽略无效行，或处理 -ERR 已在下面处理
			if strings.HasPrefix(line, "-ERR") {
				return "", fmt.Errorf("POP3 RETR 错误: %s", line)
			}
			continue
		}
		if line == "." {
			break
		}
		// 按 POP3 的点透明规则，如果行以 ".." 开头，应转为单个 '.'
		if strings.HasPrefix(line, "..") {
			line = line[1:]
		}
		builder.WriteString(line)
		builder.WriteString("\r\n")
	}
	if err := scanner.Err(); err != nil {
		return "", fmt.Errorf("读取 RETR 响应失败: %v", err)
	}
	return builder.String(), nil
}

func (p *Pop3) Disconnect() error {
	if p.conn == nil {
		return nil
	}
	return p.conn.Close()
}

type Pop3Conn struct {
	pop3Addr  string
	useTLS    bool
	proxyAddr string
	conn      net.Conn
	timeout   time.Duration
	// 新增：用于保证重连时的并发安全
	mu sync.Mutex
}

func NewPop3Conn(pop3Addr string, useTLS bool, proxyAddr string) *Pop3Conn {
	return &Pop3Conn{
		pop3Addr:  pop3Addr,
		useTLS:    useTLS,
		proxyAddr: proxyAddr,
		timeout:   15 * time.Second,
	}
}

func (p *Pop3Conn) SetTimeout(timeout time.Duration) *Pop3Conn {
	p.timeout = timeout
	return p
}

func (p *Pop3Conn) Connect() (net.Conn, error) {
	// 准备 Dial 函数：支持直接连接或通过 SOCKS5 代理
	var dial func(network, addr string) (net.Conn, error)
	if p.proxyAddr != "" {
		// 使用 SOCKS5 代理
		// proxyAddr 可能包含 user:pass@host:port，需要拆分认证信息和地址
		u, err := url.Parse(p.proxyAddr)
		if err != nil || u.Host == "" {
			return nil, fmt.Errorf("无效的代理地址: %w", err)
		}
		var auth *proxy.Auth
		if u.User != nil {
			user := u.User.Username()
			pass, _ := u.User.Password()
			auth = &proxy.Auth{
				User:     user,
				Password: pass,
			}
		}
		socksAddr := u.Host // host:port
		socksDialer, err := proxy.SOCKS5("tcp", socksAddr, auth, proxy.Direct)
		if err != nil {
			return nil, fmt.Errorf("创建 SOCKS5 拨号器失败: %w", err)
		}
		dial = func(network, addr string) (net.Conn, error) {
			return socksDialer.Dial(network, addr)
		}
	} else {
		// 直接连接
		netDialer := &net.Dialer{Timeout: p.timeout}
		dial = netDialer.Dial
	}

	// 使用 goroutine + channel 来实现超时控制（兼容不支持 context 的 socksDialer）
	type dialResult struct {
		conn net.Conn
		err  error
	}
	ch := make(chan dialResult, 1)
	go func() {
		c, e := dial("tcp", p.pop3Addr)
		ch <- dialResult{conn: c, err: e}
	}()

	select {
	case res := <-ch:
		if res.err != nil {
			return nil, fmt.Errorf("代理连接失败: %w", res.err)
		}
		conn := res.conn
		// 记录连接，便于 Disconnect 使用
		p.conn = conn

		// 如果需要 TLS，则在建立的连接上进行 TLS 握手
		if p.useTLS {
			tlsConn, err := p.tlsWrap(conn, nil)
			if err != nil {
				p.conn = nil
				return nil, err
			}
			p.conn = tlsConn
			return tlsConn, nil
		}
		return conn, nil
	case <-time.After(p.timeout):
		return nil, fmt.Errorf("连接代理超时")
	}
}

func (p *Pop3Conn) tlsWrap(conn net.Conn, tlsConfig *tls.Config) (net.Conn, error) {
	// 如果没有提供 tlsConfig，创建默认配置并设置 ServerName（用于 SNI）
	if tlsConfig == nil {
		host, _, _ := net.SplitHostPort(p.pop3Addr)
		tlsConfig = &tls.Config{
			ServerName: host,
		}
	} else {
		// 如果调用者提供了 tlsConfig 但没有设置 ServerName 且没有选择跳过验证，则用 pop3Addr 的主机填充 ServerName
		if tlsConfig.ServerName == "" && !tlsConfig.InsecureSkipVerify {
			host, _, _ := net.SplitHostPort(p.pop3Addr)
			tlsConfig = tlsConfig.Clone() // 复制以避免修改外部传入的对象
			tlsConfig.ServerName = host
		}
	}
	tlsConn := tls.Client(conn, tlsConfig)

	// 设置握手超时，并在失败时确保底层连接被关闭
	if err := tlsConn.SetDeadline(time.Now().Add(p.timeout)); err != nil {
		_ = conn.Close()
		return nil, fmt.Errorf("设置 TLS 握手超时失败: %w", err)
	}
	if err := tlsConn.Handshake(); err != nil {
		_ = conn.Close()
		return nil, fmt.Errorf("TLS 握手失败: %w", err)
	}
	// 清除 deadline，交由调用者控制
	_ = tlsConn.SetDeadline(time.Time{})
	return tlsConn, nil
}

func (p *Pop3Conn) Disconnect() {
	if p.conn != nil {
		_ = p.conn.Close()
		p.conn = nil
	}
}

func (p *Pop3Conn) IsNullOrEmpty() bool {
	return p.conn == nil
}

func (p *Pop3Conn) IsClose() bool {
	// 如果没有连接，视为已关闭
	if p.conn == nil {
		return true
	}

	// 尝试通过非阻塞读取判断连接状态：
	// 1) 设置非常短的读取 deadline
	// 2) 尝试读取一个字节
	//  - 如果返回 io.EOF 或其它非超时错误，表示连接已关闭或错误
	//  - 如果返回超时错误（net.Error 且 Timeout() == true），表示连接仍然可用（只是没有数据）
	//  - 如果读取到数据，则连接可用（注意：读取会消费该字节，这里我们不希望改变连接流）
	// 为了避免消费数据，我们在发现读取到字节时不再将其放回（这是对流的轻微破坏）。
	// 在 POP3 的使用场景中，通常不会在空闲时发生突发字节，因此此方法足够实用。
	// 如果需要严格不破坏流，应使用底层 socket peek（不在标准库中直接可用）。

	// 设置短超时
	deadline := time.Now().Add(300 * time.Millisecond)
	_ = p.conn.SetReadDeadline(deadline)

	var one [1]byte
	_, err := p.conn.Read(one[:])

	// 清除 deadline（忽略错误）
	_ = p.conn.SetReadDeadline(time.Time{})

	if err == nil {
		// 读取到数据，连接仍然打开
		return false
	}
	// 如果是超时错误，则连接可用，只是没有数据可读
	var ne net.Error
	if errors.As(err, &ne) && ne.Timeout() {
		return false
	}
	// 如果是 EOF 或其他错误，认为连接已关闭
	if err == io.EOF {
		return true
	}
	// 其余错误也认为连接不可用/已关闭
	return true
}

func (p *Pop3Conn) Reconnect() error {
	p.mu.Lock()
	defer p.mu.Unlock()

	// 如果当前连接存在且可用，不需要重连
	if p.conn != nil {
		// 使用 IsClose 判断（接收者类型改为指针以使用 p.conn）
		if !p.IsClose() {
			return nil
		}
		// 已不可用，则先关闭旧连接
		_ = p.conn.Close()
		p.conn = nil
	}

	attempts := 3
	interval := 1 * time.Second
	try := 0
	for {
		try++
		_, err := p.Connect()
		if err == nil {
			// Connect 已在成功时设置 p.conn
			return nil
		}

		// 如果设置了 attempts 且达到次数则返回错误
		if try >= attempts {
			return fmt.Errorf("重连失败，最后一次错误：%w", err)
		}

		// 等待一段时间后继续重试（指数退避，上限 30s）
		sleep := interval
		if sleep > 30*time.Second {
			sleep = 30 * time.Second
		}
		time.Sleep(sleep)
		// 指数退避翻倍
		interval = interval * 2
	}
}

func (p *Pop3Conn) EnsureConnected() (net.Conn, error) {
	// 快速检查（无需加锁）
	if p.conn != nil && !p.IsClose() {
		return p.conn, nil
	}
	// 尝试重连
	if err := p.Reconnect(); err != nil {
		return nil, err
	}
	return p.conn, nil
}
